/*
 * Copyright (c) 2011-2012 Alexander Dubu
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * o  Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * o  Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * o  Neither the name Axil nor the names of its contributors may be used to
 *    endorse or promote products derived from this software without specific
 *    prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package axil.compiler.ast;

import axil.api.AxilObject;
import axil.compiler.Compilation;
import axil.compiler.Scope;
import axil.compiler.lexer.Literal;
import axil.definition.UnitDefinition;
import axil.engine.Repository;
import axil.stdlib.core.type.Atom;
import axil.stdlib.money.type.Currency;
import axil.stdlib.money.type.Money;
import axil.stdlib.numbers.type.Decimal;
import axil.stdlib.numbers.type.FloatingPoint;
import axil.vm.opcode.OpLiteral;
import axil.vm.opcode.Opcode;

import static axil.framework.Functions.*;


/**
 * An AST node defining a literal value, such as the string "abc" or the extended
 * literal 32ft.
 */
public class LiteralNode extends AbstractNode {
	private Literal literal;
	private Atom value;


	public LiteralNode(Literal literal) {
		super(literal);
		this.literal = literal;
	}


	public Literal value() {
		return literal;
	}


	/**
	 * Provide a brief description of what this node is. This should include the
	 * kind of thing it is, not the contents of the thing.
	 */
	public String what() {
		return "literal value " + codify(literal.value());
	}


	/**
	 * Dump the content of this node to the diagnostic device. This is strictly
	 * for internal diagnostics, not for application use.
	 */
	public void dump(int indent) {
		debug(indent, literal);
	}


	private void unitize(Compilation context, AxilObject a, String suffix) {
		UnitDefinition unit = Repository.instance().unit(suffix);
		if (unit == null) {
			throw problem("unknown-unit", nv("unit", suffix));
		}
		if (context.allows(unit.type())) {
			if (unit.supports(a.type())) {
				value = (Atom)unit.from(a);
			} else {
				throw problem("unsupported-base-type",
				              nv("type", unit.type().identity()),
				              nv("base", value.type().identity()));
			}
		} else {
			/*
			 * In this case, we actually know the unit, but that
			 * type is not one of the supported types for this
			 * compilation context.
			 */
			throw problem("unknown-unit", nv("unit", suffix));
		}
	}


	/**
	 * Compile this node, verifying correctness and performing any optimizations
	 * possible. The compilation context and scope given are never null but may
	 * not contain the desired functions and identifiers.
	 */
	public Node compile(Compilation context, Scope scope) {
		String suffix = literal.suffix();
		Object raw = literal.value();
		switch(literal.species()) {
		case INTEGER:
		case DECIMAL:
			if (empty(suffix)) {
				value = (Atom)axil(raw);
			} else {
				if (suffix.equals("fp")) {
					value = FloatingPoint.from(((Number)raw).doubleValue());
				} else {
					unitize(context, axil(raw), suffix);
				}
			}
			break;

		case FRACTION:
		case STRING:
		case CHAR:
			if (empty(suffix)) {
				value = (Atom)axil(raw);
			} else {
				unitize(context, axil(raw), suffix);
			}
			break;

		case PERCENT:
		case DATE:
		case TIME:
			if (! empty(suffix)) {
				throw abort(
				    "This type does not support extended literal syntax.",
				    nv("type", literal.species()));
			}
			value = (Atom)literal.value();
			break;

		case MONEY:
			Currency currency;
			if ((literal.prefix() != 0) && empty(suffix)) {
				/*
				 * If we have a prefix, but no such (as would be the case
				 * for something like $25.00), then we lookup the currency
				 * using the currency symbol.
				 */
				currency = Currency.from(literal.prefix());
				if (currency == null) {
					throw problem("unknown-currency-symbol",
					              nv("symbol", literal.prefix()));
				}
			} else {
				assert ! empty(suffix);
				currency = Currency.from(suffix);
				if (currency == null) {
					throw problem("unknown-currency",
					              nv("currency", suffix));
				}
			}
			value = new Money(Decimal.from((Number)raw), currency);
			break;

		default:
			throw unreachable();
		}
		return this;
	}


	/**
	 * Emit VM instructions that represents an executable version of this AST
	 * node. The instruction returned cannot be null. An exception is thrown if
	 * any latent defects in the AST are discovered.
	 */
	public Opcode emit() {
		return new OpLiteral(this, value);
	}
}
